﻿using CandyMergeTool.Internals;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Input;
using System.Windows.Forms;
using CandyMergeTool.Model;
using CandyMergeTool.Settings;
using System.Drawing.Drawing2D;
using System.Threading;

namespace CandyMergeTool
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private readonly Settings.Settings m_settings;
        private readonly Form m_form;

        private Graphics m_graphics;
        private Pixel m_startingPixel;
        private List<Movement> m_possibleMovements;
        private System.Timers.Timer m_timerAuto;

        public MainWindowViewModel()
        {
            m_settings = Settings.Settings.Load();

            m_timerAuto = new System.Timers.Timer(1000);
            m_timerAuto.Elapsed += async (s, e) => await DetectCombination();

            m_possibleMovements = new List<Movement>();

            m_form = new Form();
            PrepareForm();
        }

        #region CONFIGURATION
        private void PrepareForm()
        {
            m_form.BackColor = Color.LightGreen;
            m_form.TransparencyKey = Color.LightGreen;
            m_form.Size = new Size(Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height);
            m_form.Location = new Point(0,0);
            m_form.StartPosition = FormStartPosition.Manual;
            m_form.FormBorderStyle = FormBorderStyle.None;
            m_form.WindowState = FormWindowState.Maximized;
            m_form.MinimizeBox = false;
            m_form.MaximizeBox = false;
            m_form.ControlBox = false;
            m_form.TopMost = true;  //For development in case something goes wrong
            m_form.BringToFront();
            m_form.Show();

            m_graphics = m_form.CreateGraphics();
        }
        #endregion

        #region CLEAR
        public ICommand ResetCommand
        {
            get { return new RelayCommand((o) => { m_timerAuto.Stop(); AutoDetection = false; Reset(); }, (o) => CandyCrushFound); }
        }

        private void Reset()
        {
            m_possibleMovements = new List<Movement>();
            m_graphics.Clear(Color.LightGreen);
        }
        #endregion

        #region DETECT
        private bool m_autoDetection;
        public bool AutoDetection
        {
            get { return m_autoDetection; }
            set
            {
                if (m_autoDetection != value)
                {
                    m_autoDetection = value;
                    RaisePropertyChanged("AutoDetection");
                }
            }
        }

        public ICommand AutoDetectCombinationCommand
        {
            get { return new RelayCommand(async (o) => await AutoDetectCombination()); }
        }

        private async Task AutoDetectCombination()
        {
            if (m_autoDetection)
            {
                await DetectCombination();
                m_timerAuto.Start();
            }
            else
            {
                m_timerAuto.Stop();
                Reset();
            }
        }

        public ICommand DetectCombinationCommand
        {
            get { return new RelayCommand(async (o) => await DetectCombination(),(o) => CandyCrushFound); }
        }

        private CandyCrush GetCandyCrush()
        {
            var sc = new ScreenCapture();

            using (var img = sc.CaptureScreen())
            {
                using (var bitmap = new Bitmap(img))
                {
                    return LoadCandyCrush(bitmap);
                }
            }
        }

        private async Task DetectCombination()
        {
            await Task.Factory.StartNew(() =>
                {
                    var candyCrush = GetCandyCrush();

                    Reset();

                    for (int y = 0; y < m_settings.NbVerticalCells; y++)
                    {
                        for (int x = 0; x < m_settings.NbHorizontalCells; x++)
                        {
                            var cell = candyCrush[x, y];
                            if (cell == null) continue;

                            // Lignes de 3
                            var cells = GetCellsToProcess(candyCrush.Cells, x, y, Internals.Orientation.Horizontal, 3);
                            if (cells != null)
                            {
                                var candyNeededIn = GetCandyNeededIn(cells, Internals.Orientation.Horizontal);

                                if (candyNeededIn != null)
                                {
                                    CheckMovements(candyCrush.Cells, candyNeededIn, ActionResult.Merge3);
                                }
                            }

                            // Rangée de 3
                            cells = GetCellsToProcess(candyCrush.Cells, x, y, Internals.Orientation.Vertical, 3);
                            if (cells != null)
                            {
                                var candyNeededIn = GetCandyNeededIn(cells, Internals.Orientation.Vertical);

                                if (candyNeededIn != null)
                                {
                                    CheckMovements(candyCrush.Cells, candyNeededIn, ActionResult.Merge3);
                                }
                            }

                            // Lignes de 4
                            cells = GetCellsToProcess(candyCrush.Cells, x, y, Internals.Orientation.Horizontal, 4);
                            if (cells != null)
                            {
                                var candyNeededIn = GetCandyNeededIn(cells, Internals.Orientation.Horizontal);

                                if (candyNeededIn != null)
                                {
                                    CheckMovements(candyCrush.Cells, candyNeededIn, ActionResult.Merge4);
                                }
                            }

                            // Rangée de 4
                            cells = GetCellsToProcess(candyCrush.Cells, x, y, Internals.Orientation.Vertical, 4);
                            if (cells != null)
                            {
                                var candyNeededIn = GetCandyNeededIn(cells, Internals.Orientation.Vertical);

                                if (candyNeededIn != null)
                                {
                                    CheckMovements(candyCrush.Cells, candyNeededIn, ActionResult.Merge4);
                                }
                            }

                            // Lignes de 5
                            cells = GetCellsToProcess(candyCrush.Cells, x, y, Internals.Orientation.Horizontal, 5);
                            if (cells != null)
                            {
                                var candyNeededIn = GetCandyNeededIn(cells, Internals.Orientation.Horizontal);

                                if (candyNeededIn != null)
                                {
                                    CheckMovements(candyCrush.Cells, candyNeededIn, ActionResult.Merge5);
                                }
                            }

                            // Rangée de 5
                            cells = GetCellsToProcess(candyCrush.Cells, x, y, Internals.Orientation.Vertical, 5);
                            if (cells != null)
                            {
                                var candyNeededIn = GetCandyNeededIn(cells, Internals.Orientation.Vertical);

                                if (candyNeededIn != null)
                                {
                                    CheckMovements(candyCrush.Cells, candyNeededIn, ActionResult.Merge5);
                                }
                            }
                        }
                    }

                    DrawMovements();
                });
        }

        private void DrawMovements()
        {
            foreach (var movement in m_possibleMovements)
            {
                var startingX = m_startingPixel.X + (movement.Source.X * m_settings.CellWidth);
                var startingY = m_startingPixel.Y + (movement.Source.Y * m_settings.CellHeight);
                int endingX = 0;
                int endingY = 0;
                switch(movement.Direction)
                {
                    case Direction.FromLeft:
                        startingX += m_settings.CellWidth / 4 * 3;
                        startingY += m_settings.CellHeight / 2;
                        endingX = startingX + m_settings.CellWidth / 2;
                        endingY = startingY;
                        break;
                    case Direction.FromTop:
                        startingX += m_settings.CellWidth / 2;
                        startingY += m_settings.CellHeight / 4 * 3;
                        endingX = startingX;
                        endingY = startingY + m_settings.CellHeight / 2;
                        break;
                    case Direction.FromRight:
                        startingX += m_settings.CellWidth / 4;
                        startingY += m_settings.CellHeight / 2;
                        endingX = startingX - m_settings.CellWidth / 2;
                        endingY = startingY;
                        break;
                    case Direction.FromBottom:
                        startingX += m_settings.CellWidth / 2;
                        startingY += m_settings.CellHeight / 4;
                        endingX = startingX;
                        endingY = startingY - m_settings.CellHeight / 2;
                        break;
                }

                Pen pen = new Pen(Color.LightGreen);
                pen.StartCap = LineCap.NoAnchor;
                pen.EndCap = LineCap.ArrowAnchor;

                switch (movement.ActionResult)
                {
                    case ActionResult.Merge3:
                        pen.Color = Color.FromArgb(0, 0, 255);
                        pen.Width = 6;
                        break;
                    case ActionResult.Merge4:
                        pen.Color = Color.FromArgb(0, 255, 0);
                        pen.Width = 8;
                        break;
                    case ActionResult.Merge5:
                        pen.Color = Color.FromArgb(255, 0, 0);
                        pen.Width = 10;
                        break;
                }

                m_graphics.DrawLine(pen, startingX, startingY, endingX, endingY);
            }
        }

        private void AddMovement(Movement movement)
        {
            var existingMovement = m_possibleMovements.FirstOrDefault(pm => pm.SameMovementAs(movement));
            if (existingMovement != null)
            {
                if (existingMovement.ActionResult < movement.ActionResult)
                {
                    m_possibleMovements.Remove(existingMovement);
                    m_possibleMovements.Add(movement);
                }
            }
            else
            {
                m_possibleMovements.Add(movement);
            }
        }

        private void CheckMovements(Cell[,] allCells, CandyNeededIn candyNeededIn, ActionResult result)
        {
            foreach (var direction in candyNeededIn.Directions)
            {
                switch (direction)
                {
                    case Direction.FromLeft:
                        if (candyNeededIn.Cell.X > 0)
                        {
                            var cell = allCells[candyNeededIn.Cell.X - 1,candyNeededIn.Cell.Y];
                            if (cell != null && cell.CandyType == candyNeededIn.CandyType)
                            {
                                var movement = new Movement(
                                    allCells[candyNeededIn.Cell.X - 1, candyNeededIn.Cell.Y],
                                    allCells[candyNeededIn.Cell.X, candyNeededIn.Cell.Y],
                                    Direction.FromLeft,
                                    result);

                                AddMovement(movement);
                            }
                        }
                        break;
                    case Direction.FromTop:
                        if (candyNeededIn.Cell.Y > 0)
                        {
                            var cell = allCells[candyNeededIn.Cell.X ,candyNeededIn.Cell.Y - 1];
                            if (cell != null && cell.CandyType == candyNeededIn.CandyType)
                            {
                                var movement = new Movement(
                                    allCells[candyNeededIn.Cell.X, candyNeededIn.Cell.Y - 1],
                                    allCells[candyNeededIn.Cell.X, candyNeededIn.Cell.Y],
                                    Direction.FromTop,
                                    result);

                                AddMovement(movement);
                            }
                        }
                        break;
                    case Direction.FromRight:
                        if (candyNeededIn.Cell.X < m_settings.NbHorizontalCells - 1)
                        {
                            var cell = allCells[candyNeededIn.Cell.X + 1,candyNeededIn.Cell.Y];
                            if (cell != null && cell.CandyType == candyNeededIn.CandyType)
                            {
                                var movement = new Movement(
                                    allCells[candyNeededIn.Cell.X + 1, candyNeededIn.Cell.Y],
                                    allCells[candyNeededIn.Cell.X, candyNeededIn.Cell.Y],
                                    Direction.FromRight,
                                    result);

                                AddMovement(movement);
                            }
                        }
                        break;
                    case Direction.FromBottom:
                        if (candyNeededIn.Cell.Y < m_settings.NbVerticalCells - 1)
                        {
                            var cell = allCells[candyNeededIn.Cell.X,candyNeededIn.Cell.Y + 1];
                            if (cell != null && cell.CandyType == candyNeededIn.CandyType)
                            {
                                var movement = new Movement(
                                    allCells[candyNeededIn.Cell.X, candyNeededIn.Cell.Y + 1],
                                    allCells[candyNeededIn.Cell.X, candyNeededIn.Cell.Y],
                                    Direction.FromBottom,
                                    result);

                                AddMovement(movement);
                            }
                        }
                        break;
                }
            }
        }

        private Cell[] GetCellsToProcess(Cell[,] allCells, int startingX, int startingY, Internals.Orientation orientation, int nbCells)
        {
            Cell[] cells = null;

            switch (orientation)
            {
                case Internals.Orientation.Horizontal:
                    if (m_settings.NbHorizontalCells > startingX + nbCells)
                    {
                        cells = new Cell[nbCells];
                        for (int i = 0; i < nbCells; i++)
                        {
                            if (allCells[startingX + i,startingY] == null) return null;
                            cells[i] = allCells[startingX + i,startingY];
                        }
                    }
                    break;
                case Internals.Orientation.Vertical:
                    if (m_settings.NbVerticalCells > startingY + nbCells)
                    {
                        cells = new Cell[nbCells];
                        for (int i = 0; i < nbCells; i++)
                        {
                            if (allCells[startingX,startingY + i] == null) return null;
                            cells[i] = allCells[startingX,startingY + i];
                        }
                    }
                    break;
            }

            return cells;
        }

        private CandyNeededIn GetCandyNeededIn(Cell[] cells, Internals.Orientation orientation)
        {
            var cellsByCandy = cells.GroupBy(c => c.CandyType);
            if (cellsByCandy.Count() == 2)
            {
                var cellToTest = cellsByCandy.FirstOrDefault(cbc => cbc.Count() == 1);
                if (cellToTest != null)
                {
                    var candyNeeded = cellsByCandy.First(cbc => cbc.Key != cellToTest.Key).Key;
                    var cell = cellToTest.First();
                    var index = Array.IndexOf(cells, cell);

                    var directions = new List<Direction>();

                    if(orientation == Internals.Orientation.Horizontal)
                    {
                        if (index == 0) directions.Add(Direction.FromLeft);
                        directions.Add(Direction.FromTop);
                        directions.Add(Direction.FromBottom);
                        if (index == cells.Length - 1) directions.Add(Direction.FromRight);
                    }
                    else if (orientation == Internals.Orientation.Vertical)
                    {
                        if (index == 0) directions.Add(Direction.FromTop);
                        directions.Add(Direction.FromLeft);
                        directions.Add(Direction.FromRight);
                        if (index == cells.Length - 1) directions.Add(Direction.FromBottom);
                    }

                    return new CandyNeededIn(candyNeeded, cell, directions);
                }
            }

            return null;
        }

        private CandyCrush LoadCandyCrush(Bitmap bitmap)
        {
            var candyCrush = new CandyCrush(m_settings.NbHorizontalCells,m_settings.NbVerticalCells);

            for (int y = 0; y < m_settings.NbVerticalCells; y++)
            {
                for (int x = 0; x < m_settings.NbHorizontalCells; x++)
                {
                    candyCrush[x,y] = GetCell(x,y,bitmap);
                }
            }

            return candyCrush;
        }

        private Cell GetCell(int cellX,int cellY,Bitmap bitmap)
        {
            var definition = GetCandyDefinition(cellX,cellY,bitmap);
            if (definition == null) return null;

            return new Cell(cellX,cellY,definition.CandyType,definition.CandySpecialType);
        }

        private CandyDefinition GetCandyDefinition(int cellX,int cellY,Bitmap bitmap)
        {
            var startingX = m_startingPixel.X + (cellX * m_settings.CellWidth);
            var startingY = m_startingPixel.Y + (cellY * m_settings.CellHeight);

            foreach (var definition in m_settings.CandyDefinitions)
            {
                bool candyFound = true;
                foreach (var pixelColor in definition.PixelColors)
                {
                    var color = bitmap.GetPixel(startingX + pixelColor.X,startingY + pixelColor.Y);
                    if (color.R == pixelColor.R && color.G == pixelColor.G && color.B == pixelColor.B)
                    {
                        continue;
                    }
                    else
                    {
                        candyFound = false;
                        break;
                    }
                }

                if (candyFound) return definition;
            }

            return null;
        }
        #endregion

        #region FOUND
        private bool m_candyCrushFound;
        public bool CandyCrushFound
        {
            get { return m_candyCrushFound; }
            set
            {
                if (m_candyCrushFound != value)
                {
                    m_candyCrushFound = value;
                    RaisePropertyChanged("CandyCrushFound");
                }
            }
        }

        private bool m_inCandyCrushSearching;
        public bool InCandyCrushSearching
        {
            get { return m_inCandyCrushSearching; }
            set
            {
                if (m_inCandyCrushSearching != value)
                {
                    m_inCandyCrushSearching = value;
                    RaisePropertyChanged("InCandyCrushSearching");
                    CommandManager.InvalidateRequerySuggested();
                }
            }
        }

        public ICommand FindCandyCrushCommand { get { return new RelayCommand((o) => FindCandyCrush(),(o) => !InCandyCrushSearching); } }
        private async void FindCandyCrush()
        {
            m_timerAuto.Stop();
            Reset();

            InCandyCrushSearching = true;

            var pixel = await Task<Pixel>.Factory.StartNew(() =>
                {
                    var sc = new ScreenCapture();
                    var img = sc.CaptureScreen();
                    var bitmap = new Bitmap(img);

                    for (int y = 0; y < img.Height; y++)
                    {
                        for (int x = 0; x < img.Width; x++)
                        {
                            var color = bitmap.GetPixel(x,y);
                            if (color.R == 211 && color.G == 147 && color.B == 125)
                            {
                                var x1 = x + 1;
                                color = bitmap.GetPixel(x1,y);
                                if (color.R == 214 && color.G == 150 && color.B == 127)
                                {
                                    var x2 = x + 2;
                                    color = bitmap.GetPixel(x2,y);
                                    if (color.R == 217 && color.G == 151 && color.B == 129)
                                    {
                                        return new Pixel(x + m_settings.LeftFromStart, y + m_settings.TopFromStart);
                                    }
                                }
                            }
                        }
                    }

                    return null;
                });

            if (pixel != null)
            {
                m_startingPixel = pixel;
                CandyCrushFound = true;
            }
            else
            {
                Reset();
                m_startingPixel = null;
                CandyCrushFound = false;
            }

            InCandyCrushSearching = false;
        }
        #endregion

        #region PROPERTYCHANGED
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string name)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                PropertyChanged(this,new PropertyChangedEventArgs(name));
            }
        }
        #endregion
    }
}
